#include "mpvplayer.h"
#include <QFile>
#include "m3u8parser.h"

MpvPlayer::MpvPlayer(QWidget *parent)
    : QWidget{parent}
    , _store_dir("D:/video/vod/cache/")
    , _m3u8_parser(new M3u8Parser(_store_dir))
    , _mpv(nullptr)
{
    createMpvPlayer(parent);
    connect(_m3u8_parser.get(), &M3u8Parser::hlsRequestSuccessSignal, this, &MpvPlayer::hlsRequestSuccessSlots);
    connect(_m3u8_parser.get(), &M3u8Parser::hlsRequestFailedSignal, this, &MpvPlayer::hlsRequestFailedSlots);
    connect(_m3u8_parser.get(), &M3u8Parser::pieceDownloadSuccessSignal, this, &MpvPlayer::pieceDownloadSuccessSlots);
    connect(_m3u8_parser.get(), &M3u8Parser::pieceDownloadFailSignal, this, &MpvPlayer::pieceDownloadFailSlots);
}
MpvPlayer::~MpvPlayer()
{
    //释放mpv实例
    if (_mpv) mpv_terminate_destroy(_mpv);
    _mpv = nullptr;

}

static void wakeup(void *ctx) {
    MpvPlayer *mvpPlayer = (MpvPlayer *)ctx;
    emit mvpPlayer->mpvEvents();
}

void MpvPlayer::createMpvPlayer(QWidget *videoWin){

    // 创建mpv实例
    _mpv = mpv_create();
    if (!_mpv) throw std::runtime_error("can't create mpv instance");
    if (mpv_initialize(_mpv) < 0) {
        qDebug() << "初始化mpv实例失败！";
        mpv_destroy(_mpv);
        emit this->hlsPlayErrorSignal();
        return;
    }

    // 将视频子窗口的窗口ID传递给mpv wid选项
    int64_t wid = videoWin->winId();
    mpv_set_option(_mpv, "wid", MPV_FORMAT_INT64, &wid);
    // mpv_set_option_string(_mpv, "cache", "10485760");
    //mpv_set_option_string(_mpv, "hr-seek", "yes"); // 开启精确跳转进度，而不是跳转到最近的关键帧 -- 目前并未看到效果...
    // 启用默认绑定，因为我们很懒。 通常，播放者使用mpv作为后端将实现其自己的键绑定
    // mpv_set_option_string(_mpv, "input-default-bindings", "yes");
    // // 启用键盘输入在X11 Window上
    // mpv_set_option_string(_mpv, "input-vo-keyboard", "yes");

    /*
     *  mpv_observe_property() 函数可以用来订阅 libmpv 的各种属性变化事件。根据搜索结果，以下是一些可以观察的视频属性：
            duration：视频的总时长。
            time-pos：当前播放进度。
            track-list：轨道列表，可以用来获取视频、音频和字幕轨道的信息。
            chapter-list：章节列表，可以用来获取视频的章节信息。
            width 和 height：视频的宽度和高度。
            estimated-vf-fps：估计的视频帧率。
            volume：当前音量。
            mute：是否静音。
            video-aspect：视频的纵横比。
            speed：播放速度。
            pause：播放是否暂停。
    */
    // 让我们通过MPV_EVENT_PROPERTY_CHANGE接收属性更改事件，如果这属性更改了
    mpv_observe_property(_mpv, 0, "time-pos", MPV_FORMAT_INT64);
    mpv_observe_property(_mpv, 0, "duration", MPV_FORMAT_INT64);

    // 从这里开始，将调用唤醒功能。 回调可以来自任何线程，因此我们使用QueuedConnection机制以线程安全的方式中继唤醒
    connect(this, &MpvPlayer::mpvEvents, this, &MpvPlayer::onMpvEvents, Qt::QueuedConnection);
    mpv_set_wakeup_callback(_mpv, wakeup, this);


    // 判断mpv实例是否成功初始化
    // if (mpv_initialize(_mpv) < 0) throw std::runtime_error("mpv failed to initialize");
}
void MpvPlayer::stop(){
    int s = 1;
    /*  mpv_set_property_async
            time-pos：设置视频的播放位置（跳转）。
            pause：设置播放器的暂停状态，可以设置为暂停或播放。
            mute：设置播放器的静音状态。
            volume：设置播放器的音量。
            wid：设置播放器在哪个窗口控件上显示。
            input-default-bindings：设置是否启用默认的输入键绑定。
            input-vo-keyboard：设置是否启用键盘输入。
            loop：设置播放时是否循环。
            hwdec：设置硬件加速的模式。
            rtsp-transport：设置 RTSP 传输协议。
            network-timeout：设置网络超时时间。
    */
    mpv_set_property_async(_mpv, 0, "pause", MPV_FORMAT_FLAG, &s);
}
void MpvPlayer::start() {
    int s = 0;
    mpv_set_property_async(_mpv, 0, "pause", MPV_FORMAT_FLAG, &s);
}
void MpvPlayer::setPos(int64_t sec) {
    if (_is_hls_video) {
        M3u8Node piece;
        bool ret = _m3u8_parser->seek(sec, piece);
        if (ret == false) {
            emit this->hlsPlayErrorSignal();
        }else {
            if (_cur_piece.piece_idx != piece.piece_idx){
                _cur_piece = piece;
                const char *args[] = {"stop", NULL};
                mpv_command(_mpv, args); //结束当前正在播放的分片
                if (_cur_piece.download_statu != NodeStatu::DOWNLOADED) {
                    _m3u8_parser->downloadPiece(_cur_piece.piece_idx);//开始下载对应的分片
                    emit this->pieceLoadingSignal();
                }else {
                    qDebug() << "播放视频分片：[" << _cur_piece.piece_idx << "]; 偏移量：[" << _cur_piece.offset << "]" << "路径:" << _cur_piece.path;
                    const QByteArray c_filename = _cur_piece.path.toUtf8();
                    const char *args[] = {"loadfile", c_filename.data(), NULL};
                    // QString s_offset = QString::number(_cur_piece.offset);
                    // const QByteArray c_offset = s_offset.toUtf8();
                    // const char *args[] = {"loadfile", c_filename.data(), "seek", c_offset.data(), "absolute", NULL};
                    mpv_command(_mpv, args);
                    this->start();
                    //这里的跳转并不生效，应该是因为跳转只能跳转到对应位置附近的关键帧开始播放吧。
                    //mpv_set_property_async(_mpv, 0, "time-pos", MPV_FORMAT_INT64, &_cur_piece.offset);
                    emit this->pieceLoadedSignal();
                }
            }else {
                mpv_set_property_async(_mpv, 0, "time-pos", MPV_FORMAT_INT64, &piece.offset);
            }
        }
    } else {
        mpv_set_property_async(_mpv, 0, "time-pos", MPV_FORMAT_INT64, &sec);
    }
}
void MpvPlayer::setSpeed(double speed) {
    mpv_set_property_async(_mpv, 0, "speed", MPV_FORMAT_DOUBLE, &speed);
}
void MpvPlayer::setMute(bool flag) {
    int64_t val = flag ? 1 : 0;
    mpv_set_property_async(_mpv, 0, "mute", MPV_FORMAT_FLAG, &val);
}
void MpvPlayer::setVolume(int64_t volume) {
    mpv_set_property(_mpv, "volume", MPV_FORMAT_INT64, &volume);
}
void MpvPlayer::playLocalVideo(const QString &filename){
    //播放文件
    qDebug() << "开始播放视频：" << filename;
    _is_hls_video = false;
    const QByteArray c_filename = filename.toUtf8();
    const char *args[] = {"loadfile", c_filename.data(), NULL};
    // 与mpv_command相同，但异步运行命令来避免阻塞，直到进程终止
    mpv_command_async(_mpv, 0, args);

}
void MpvPlayer::playHLSVideo(const QString &m3u8_url) {
    _is_hls_video = true;
    _m3u8_parser->hlsRequest(m3u8_url);
    emit this->pieceLoadingSignal();
}

void MpvPlayer::hlsRequestSuccessSlots(){
    qDebug() << "M3U8文件下载并解析成功，开始下载并播放第一个分片！";
    int64_t atime = _m3u8_parser->duration();
    emit this->durationChangedSignal(atime);
    _m3u8_parser->piece(0, _cur_piece);
    _m3u8_parser->downloadPiece(0);
}
void MpvPlayer::hlsRequestFailedSlots(const QString msg){
    qDebug() << "HLS请求失败：" << msg;
    emit this->hlsRequestFailedSignal(msg);
}
void MpvPlayer::pieceDownloadSuccessSlots(int idx){
    //如果当前下载成功的分片就是正在等待播放的分片，那就播放，并下载下一个分片，否则就
    qDebug() << "视频分片下载成功：[" << idx << "]-[" << _cur_piece.piece_idx << "]";
    if (idx == _cur_piece.piece_idx) { //下载好的分片整好就是当前等待播放的分片
        qDebug() << "播放视频分片：[" << _cur_piece.piece_idx << "]; 偏移量：[" << _cur_piece.offset << "]" << "路径:" << _cur_piece.path;
        const QByteArray c_filename = _cur_piece.path.toUtf8();
        const char *args[] = {"loadfile", c_filename.data(), NULL};
        // QString s_offset = QString::number(_cur_piece.offset);
        // const QByteArray c_offset = s_offset.toUtf8();
        // const char *args[] = {"loadfile", c_filename.data(), "seek", c_offset.data(), "absolute", NULL};
        mpv_command(_mpv, args);
        this->start();
        //mpv_set_property_async(_mpv, 0, "time-pos", MPV_FORMAT_INT64, &_cur_piece.offset);//
        emit this->pieceLoadedSignal();
    }
    if (idx >= _cur_piece.piece_idx && idx < _cur_piece.piece_idx + 3){
        qDebug() << "开始下载下一个分片:" << idx + 1;
        _m3u8_parser->downloadPiece(idx + 1);
    }
}
void MpvPlayer::pieceDownloadFailSlots(int idx, const QString &msg){
    qDebug() << "分片" << idx << "请求失败：" << msg;
    emit this->hlsRequestFailedSignal(msg);
}

void MpvPlayer::onMpvEvents() {
    // 处理所有事件，直到事件队列为空
    while (_mpv)
    {
        mpv_event *event = mpv_wait_event(_mpv, 0);
        if (event->event_id == MPV_EVENT_NONE)
            break;
        handleMpvEvent(event);
    }
}
void MpvPlayer::handleMpvEvent(mpv_event *event){
    switch (event->event_id) {
        // 属性改变事件发生
        case MPV_EVENT_PROPERTY_CHANGE: {
            mpv_event_property *prop = (mpv_event_property *)event->data;
            if (strcmp(prop->name, "time-pos") == 0) {
                if (prop->format == MPV_FORMAT_INT64) {
                    if (_is_hls_video){
                        // 当前分片的起始时间 + 当前分片的播放进度 = 总的视频播放进度
                        int ctime = *(int *)prop->data;
                        qDebug() << "当前播放分片的位置：" << ctime;
                        //emit this->timePosChangedSignal(_cur_piece.stime + ctime + _cur_piece.offset);
                        emit this->timePosChangedSignal(_cur_piece.stime + ctime);
                    }else{
                        int ctime = *(int *)prop->data;
                        emit this->timePosChangedSignal(ctime);
                    }
                } else if (prop->format == MPV_FORMAT_NONE) {
                    //无效的时间戳 -- 有时候也可以用于表示一个视频播放完了
                }
            }else if (strcmp(prop->name, "duration") == 0) {
                if (prop->format == MPV_FORMAT_INT64) {
                    if (_is_hls_video){
                    }else {
                        int64_t atime = *(int64_t *)prop->data;
                        emit this->durationChangedSignal(atime);
                        qDebug() << "视频时长: " << atime;
                    }
                }
            }
        }
        break;
        case MPV_EVENT_END_FILE: {
            qDebug() << "视频文件播放完毕信号------------";
            if (_is_hls_video){
                if (_cur_piece.is_seek) {//表示当前是跳转位置，在结束上个分片时所产生的上个分片结束信号
                    _cur_piece.is_seek = false; //防止当前跳转的分片播放完毕的时候无法继续向后播放，所以修改为false
                    return;
                }
                qDebug() << "当前分片播放完毕:" << _cur_piece.piece_idx;
                // 一个分片播放完毕，则获取下一个分片进行播放，如果下个分片还未下载成功，则发送加载中信号
                if (_cur_piece.piece_idx + 1 >= _m3u8_parser->pieceCount()) {
                    qDebug() << "所有分片播放完毕！";
                    return;
                }
                _m3u8_parser->piece(_cur_piece.piece_idx + 1, _cur_piece);
                if (_cur_piece.download_statu != NodeStatu::DOWNLOADED) {
                    this->stop();
                    _m3u8_parser->downloadPiece(_cur_piece.piece_idx);
                    emit this->pieceLoadingSignal();
                }else {
                    qDebug() << "开始获取下一个分片进行播放:[" <<_cur_piece.piece_idx << "]";
                    const QByteArray c_filename = _cur_piece.path.toUtf8();
                    const char *args[] = {"loadfile", c_filename.data(), NULL};
                    // QString s_offset = QString::number(_cur_piece.offset);
                    // const QByteArray c_offset = s_offset.toUtf8();
                    // const char *args[] = {"loadfile", c_filename.data(), "seek", c_offset.data(), "absolute", NULL};
                    mpv_command(_mpv, args);
                    this->start();
                    emit this->pieceLoadedSignal();

                    M3u8Node next_piece;
                    bool ret = _m3u8_parser->piece(_cur_piece.piece_idx + 1, next_piece);
                    if (ret == true && next_piece.download_statu != NodeStatu::DOWNLOADED)
                        _m3u8_parser->downloadPiece(next_piece.piece_idx);
                }
            }else {

            }
        }
        break;
        // palyer退出事件发生
        case MPV_EVENT_SHUTDOWN: {
            mpv_terminate_destroy(_mpv);
            _mpv = NULL;
        }
        break;
        default: ;
    }
}
